/**
* @author Shang Liang
* 

* The MIT License

* Copyright (c) <2008> <Shang Liang>

* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:

* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.

* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.

*/

package com.dita {
	
	import flash.filesystem.*;
	import flash.desktop.*;
	import flash.system.*;
	import flash.display.*;
	import flash.events.*;
	import flash.net.*;
	import flash.text.*;
	import flash.utils.*;
	import flash.geom.*;
	
	/**
	 * Main Class of DiTA
	 * All variables and methods are uing "protected" so they can show up in doc. Not my normal practice. 
	 */
	public class Main extends MovieClip {
		
		/**
		 * border color for text fields
		 */
		protected const BORDER_COLOR:int = 0x999999;
		/**
		 * button rollover effect, alpha set to a value (0.2 in this case)
		 */
		protected const BTN_ALPHA:Number = 0.2;
		/**
		 * shared object name
		 */
		protected const SO_NAME:String = "dita";
		/**
		 * out put file name
		 */
		protected const OUTPUT_FILE:String="asdoc";
		
		/**
		 * help text dictionary to lookup using corresponding text field
		 */
		protected var helpTfDic:Dictionary;
		/**
		 * text field dictionary to lookup using corresponding button
		 */
		protected var tfBtnDic:Dictionary;
		/**
		 * currently selected text field so the file browsing knows where to put selected path/file in
		 */
		protected var currTf:TextField;
		/**
		 * array of strings that holds all classes
		 */
		protected var allClasses:Array;
		/**
		 * array of strings that stores all text field names, for reference convenience
		 */
		protected var tfNameArr:Array 
		/**
		 * array of text fields, for reference convenience
		 */
		protected var tfArr:Array;
		
		/**
		 * constructor, initalise dictionaries and user interface. read shared object data and populate the text fields with previously saved data
		 */
		public function Main() {
			helpTfDic = new Dictionary();
			tfBtnDic = new Dictionary();
			
			var valArr:Array = getCache();
			
			tfArr = new Array();
			tfNameArr= ["asdoc", "source", "output", "library", "swc", "classes", "exclude", "main", "window", "footer"];
			
			var numBtns:int = 5;
			
			for (var i:int= 0; i < tfNameArr.length; i++) {
				var tf:TextField = getChildByName(tfNameArr[i] + "_txt") as TextField;
				tfArr.push(tf);
				tf.border = true;
				tf.borderColor = BORDER_COLOR;
				helpTfDic[tf] = tf.text;
				tf.addEventListener(FocusEvent.FOCUS_IN, hFocusIn);
				tf.addEventListener(FocusEvent.FOCUS_OUT, hFocusOut);
				
				if (valArr[i]) {
					tf.text = valArr[i];
				}else{
					tf.text = "";
				}
				if (i<numBtns) {
					var btn:MovieClip = getChildByName(tfNameArr[i] + "_btn") as MovieClip;					
					tfBtnDic[btn] = tf;
					initBtn(btn);
				}
			}
			var genrateBtn:MovieClip = getChildByName("generate_btn") as MovieClip;
			initBtn(genrateBtn);
			
			NativeApplication.nativeApplication.addEventListener(Event.EXITING, hApplicationExit);
		
		}
		/**
		 * event handler for text field focus in, display help text
		 * @param	evt		focus event
		 * @eventType		FocusEvent
		 */
		protected function hFocusIn(evt:FocusEvent):void {
			var tf:TextField = evt.currentTarget as TextField;
			var htf:TextField = getChildByName("help_txt") as TextField;
			htf.text = helpTfDic[tf];
		}
		/**
		 * event handler for text field focus in, set help text to nothing
		 * @param	evt		focus event
		 * @eventType		FocusEvent
		 */
		protected function hFocusOut(evt:FocusEvent):void {
			var tf:TextField = evt.currentTarget as TextField;
			tf.text = StringUtil.trim(tf.text);
			var htf:TextField = getChildByName("help_txt") as TextField;
			htf.text = "";
		}
		/**
		 * helper function to initialise a button, add event linstners for mouse over/out/click
		 * @param	btn		movieclip of a button
		 */
		protected function initBtn(btn:MovieClip):void {
			btn.buttonMode = true;
			btn.mouseChildren = false;
			btn.addEventListener(MouseEvent.MOUSE_OVER, hMouseOver);
			btn.addEventListener(MouseEvent.MOUSE_OUT, hMouseOut);
			btn.addEventListener(MouseEvent.CLICK, hMouseClick);
		}
		/**
		 * mouse over event handler for button, set alpha to BTN_ALPHA
		 * @param	evt		mouse over event
		 * @eventType		MouseEvent.MOUSE_OVER
		 */
		protected function hMouseOver(evt:MouseEvent):void {
			var btn:MovieClip = evt.currentTarget as MovieClip;
			btn.alpha = BTN_ALPHA;
		}
		/**
		 * mouse out event handler for button, set alpha back to 1
		 * @param	evt		mouse out event
		 * @eventType		MouseEvent.MOUSE_OUT
		 */
		protected function hMouseOut(evt:MouseEvent):void {
			var btn:MovieClip = evt.currentTarget as MovieClip;
			btn.alpha = 1;
		}
		/**
		 * mouse click event handler for button, open file browser to allow user's select folder/file
		 * @param	evt		mouse click event
		 * @eventType		MouseEvent.CLICK
		 */
		protected function hMouseClick(evt:MouseEvent):void {
			var btn:MovieClip = evt.currentTarget as MovieClip;
			var tf:TextField = tfBtnDic[btn] as TextField;			
			var df:File;
			switch(btn.name) {
				case "generate_btn":
				generate();
				break;
				case "asdoc_btn":
				try{
					df= File.desktopDirectory;
					df.browseForOpen(helpTfDic[tf]);
					df.addEventListener(Event.SELECT, hAsdocPathSelect);
				}catch (e:Error){
					log(e.message);
				}
				break;
				default:
				try{
					df= File.desktopDirectory;
					df.browseForDirectory(helpTfDic[tf]);
					df.addEventListener(Event.SELECT, hDirectorySelect);
					currTf = tf;
				}catch (e:Error){
					log(e.message);
				}
			}			
		}
		/**
		 * folder select handler, insert selected folder path into corresponding text field
		 * @param	evt		Event
		 * @eventType		Event.SELECT
		 */
		protected function hDirectorySelect(evt:Event):void {
			var dir:File = evt.target as File;
			if (currTf.name == "source_txt" || currTf.name == "library_txt" || currTf.name == "swc_txt") {
				if (StringUtil.trim(currTf.text) != "")
				{
					currTf.appendText(",");
				}
				currTf.appendText("\""+dir.nativePath+"\"");
			}else {
				currTf.text = "\""+dir.nativePath+"\"";
			}
			
		}
		/**
		 * file select handler, insert selected path of asdoc executable file into corresponding text field
		 * @param	evt		Event
		 * @eventType		Event.SELECT
		 */
		protected function hAsdocPathSelect(evt:Event):void {
			var dir:File = evt.target as File;
			var tf:TextField = getChildByName("asdoc_txt") as TextField;
			if(isPc()){
				tf.text = ("\""+dir.nativePath+"\" ");
			}else {
				tf.text = ("\"./"+dir.nativePath+"\" ");
			}
		}
		/**
		 * generate the command, use all input data and scan for classes, generate a string of command and prompt the user to select a location to save
		 */
		protected function generate() :void
		{
			//              0         1         2          3       4         5         6         7        8         9
			//tfNameArr= ["asdoc", "source", "output", "library", "swc", "classes", "exclude", "main", "window", "footer"];
			var asdocPath:String = tfArr[0].text;
			var sourcePath:String = " -source-path " + StringUtil.sliptCSV(tfArr[1].text).join(" ") +" "+ StringUtil.sliptCSV(tfArr[3].text).join(" ");
			var outputPath:String = " -output "+ tfArr[2].text;
			var swcPath:String = " -library-path " + tfArr[4].text;
			var mainStr:String = " -main-title \"" + tfArr[7].text.split("\"").join("") + "\"";
			var windowStr:String = " -window-title \"" + tfArr[8].text.split("\"").join("") + "\"";
			var footerStr:String = " -footer \"" + tfArr[9].text.split("\"").join("") + "\"";
			
			var classPathArr:Array = StringUtil.sliptCSV(tfArr[1].text);
			allClasses = new Array();
			var i:int = 0;
			var j:int = 0;			
			for (i = 0; i < classPathArr.length; i++) {
				var pathStr:String = classPathArr[i];				
				allClasses=allClasses.concat(getClassNames(pathStr));
			}
			
			var classArr:Array = new Array();
			var includePackageArr:Array = new Array();
			if (tfArr[5].text!="") {
				includePackageArr = StringUtil.sliptCSV(tfArr[5].text);				
				for (i = 0; i < allClasses.length; i++) {
					for (j = 0; j < includePackageArr.length; j++) {						
						var inReg:RegExp = new RegExp(includePackageArr[j]);						
						if (inReg.test(allClasses[i])) {
							classArr.push(allClasses[i]);
							break;
						}
					}					
				}
			}else {
				classArr = allClasses;
			}
			
			if (tfArr[6].text != "") {
				var excludePackageArr:Array = StringUtil.sliptCSV(tfArr[6].text);
				for (i = 0; i < classArr.length; i++) {
					for (j = 0; j < excludePackageArr.length; j++) {
						var exReg:RegExp = new RegExp(excludePackageArr[j]);
						if (exReg.test(classArr[i])) {
							classArr[i] = "";
							break;
						}
					}					
				}
			}
			var classStr:String = " -doc-classes " + classArr.join(" ");			
			var dependencyStr:String = " -exclude-dependencies ";
			var pauseStr:String = "\n pause";
			
			var commandStr:String = asdocPath + outputPath + sourcePath + swcPath + mainStr + windowStr + footerStr + classStr + dependencyStr+pauseStr;
			var f:File = File.documentsDirectory;
			f = f.resolvePath(getOutputFileName());
			var fs:FileStream = new FileStream();
			fs.open(f, FileMode.WRITE);
			fs.writeUTFBytes(commandStr);
			fs.close();
			f.browseForSave("DiTA");
			f.addEventListener(Event.SELECT, hSaveFileSelect);
		}
		/**
		 * file select handler, insert selected path of asdoc executable file into corresponding text field
		 * @param	evt		Event
		 * @eventType		Event.SELECT
		 */
		protected function hSaveFileSelect(evt:Event) :void
		{
			var f:File = File(evt.currentTarget);
			var of:File = File.documentsDirectory.resolvePath(getOutputFileName());
			of.copyTo(f, true);
		}
		/**
		 * get the classes within a path/directory, recursively search for .as files and use the file name as class name
		 * @param	path	directory path
		 * @return			an array of class names (Strings)
		 */
		protected function getClassNames(path:String):Array {
			var classNameArr:Array = new Array();
			try{
				var dir:File = new File(path);
				classNameArr= getASFilesInFolder(dir, dir.nativePath);
			}catch (e:Error){				
				log( path + " - " + e.message);
			}
			return classNameArr;
		}
		/**
		 * scan a folder to get all actionscript files and get it's class names recursively
		 * @param	dir				folder/directory to search in
		 * @param	sourcePath		path of the source folder, it's used to determin the package name
		 * @return					an array of class names
		 */
		protected function getASFilesInFolder(dir:File, sourcePath:String):Array {
			var asFiles:Array = new Array();
			var files:Array = dir.getDirectoryListing();
						
			for (var i:int = 0; i < files.length; i++) {
				var f:File = files[i] as File;
				if (f.isDirectory) {					
					asFiles = asFiles.concat(getASFilesInFolder(f, sourcePath));
				}else {
					var ext:String = f.extension+"";
					if (ext.toLocaleLowerCase() == "as") {
						var fs:FileStream = new FileStream();
						fs.open(f, FileMode.READ);
						var content:String = fs.readUTFBytes(fs.bytesAvailable);						
						fs.close();
						var reg:RegExp = /package\s*[a-zA-Z0-9\.]*\s*{/ ;
						var packageName:String = reg.exec(content);
						if (!packageName) {
							continue;
						}
						
						packageName = dir.nativePath.substr(sourcePath.length+1);
						if (packageName.length > 0)
						{
							// pc and unix use different slashes in the path
							// pc use \
							// unix use /
							if(isPc()){
								packageName = packageName.split("\\").join(".") + ".";
							}else
							{
								packageName = packageName.split("/").join(".") + ".";
							}
						}
						asFiles.push(packageName + f.name.substr(0,f.name.length-3));
					}
				}
			}
			return asFiles;
		}
		/**
		 * 
		 * @return	Array of saved value in shared object, if there's no value saved, return an empty array
		 */
		protected function getCache():Array{
			var so:SharedObject = SharedObject.getLocal(SO_NAME, "/");
			if(so.data.valArr){
				return so.data.valArr;
			}else {
				return [];
			}
		}
		/**
		 * event handler for application exit, save all inputs into shared object
		 * @param	evt
		 * @eventType Event
		 */
		protected function hApplicationExit(evt:Event):void {
			var valArr:Array=new Array();
			for (var i:int = 0; i < tfArr.length; i++) {
				valArr[i] = tfArr[i].text;
			}
			var so:SharedObject = SharedObject.getLocal(SO_NAME, "/");
			so.data.valArr = valArr;
			so.flush(1000);
			so.close();
		}
		/**
		 * log/trace some info into the small text field at the bottom of the application
		 * @param	str
		 */
		protected function log(obj:Object):void {
			var etf:TextField = getChildByName("error_txt") as TextField;
			etf.appendText(obj.toString()+ "\n");
		}
		/**
		 * helper function to get the save file name for different os, if pc then save as .bat if not save as .sh
		 * @return			save file name
		 */
		protected function getOutputFileName() :String
		{
			if (isPc())
			{
				return  OUTPUT_FILE + ".bat";
			}else
			{
				return  OUTPUT_FILE + ".sh";
			}
		}
		/**
		 * Helper function to detect whether the system environment is PC or Unix based(mac or linux)
		 * @return
		 */
		protected function isPc():Boolean {
			return Capabilities.os.toLowerCase().indexOf("windows") > -1;
		}
		
	}
	
}
